using System;
using System.IO;
using System.Reflection;
using System.Xml;
using NUnit.Framework;
using ValidationFramework;
using ValidationFramework.Configuration;
using ValidationFramework.Extensions;
using ValidationFramework.Reflection;
using ValidationFramework.Configuration.Xml;
using ValidationFramework.Tests.TestHelpers;

namespace ValidationFramework.Tests.Configuration.Xml
{
	#region Test Model

	public class Employee : Person
	{

	}

	public class Person
	{
		private static readonly RuntimeMethodHandle setTheAgeHandle =
			typeof(Person).GetMethod("SetTheAge").MethodHandle;

		public string Name { get; set; }


		public int Age { get; set; }


		public void SetTheAge(int age)
		{
			Validation.ThrowException(new MethodKey(setTheAgeHandle), age);
			Age = age;
		}


		public void SetTheAge(long age)
		{
			Validation.ThrowException(new MethodKey(setTheAgeHandle), age);
			Age = (int)age;
		}


		public void SetTheAge2(int age)
		{
			Validation.ThrowException(new MethodKey(setTheAgeHandle), age);
			Age = age;
		}

		public static bool NameIsDane(object instanceToValidate, object context)
		{
			var person = instanceToValidate as Person;
			if (person == null)
				return true;

			return person.Name == "Dane";
		}
	}

	#endregion

	#region Fakes

	public class AlwaysFailValidator : SpecificationBase
	{
		public override bool IsSatisfiedBy(object targetToValidate)
		{
			return false;
		}
	}

	public class AlwaysFailValidatorAddr : ISpecificationXmlParser
	{
		// --- IValidatorXmlAddr Members
		public ISpecification GetSpecification(XmlElement validatorElement, RuleContext context)
		{
			return new AlwaysFailValidator();
		}
	}

	#endregion

	#region Setup/Teardown

	[TestFixture]
	public class ConfigurationServiceTestBase
	{
		protected XmlConfigurationService _xmlService;
		protected TypePolicyRepository _typePolicyRepository;
		protected MethodPolicyRepository _methodPolicyRepository;

		internal ConfigurationServiceTestBase()
		{

		}

		[SetUp]
		public void Setup()
		{
			Validation.Initialize(x => x.LoadConfigurationFromAppConfig = false);

			var Parsers = new ParserRepository();
			Parsers.AddParser(typeof(AlwaysFailValidator), new AlwaysFailValidatorAddr());
			_typePolicyRepository = new TypePolicyRepository();
			_methodPolicyRepository = new MethodPolicyRepository();

			_xmlService = new XmlConfigurationService(Parsers, new ReflectionCache(), _typePolicyRepository, _methodPolicyRepository);

		}

		public void Teardown()
		{

		}
	}

	#endregion

	[TestFixture]
	public class XmlConfigurationServiceTest
	{

		private const string propertyXmlString = @"<?xml version='1.0' encoding='utf-8' ?>
		<validationMapping xmlns='urn:validationFramework-validationDefinition-1.5'>
		  <class typeName='ValidationFramework.Tests.Configuration.Xml.Person, ValidationFramework.Tests'>
		    <property name='Name'>
		      <rule typeName='ValidationFramework.Tests.Configuration.Xml.AlwaysFailValidator, ValidationFramework.Tests' />
		    </property>
		    <property name='Age'>
		      <rule typeName='ValidationFramework.Tests.Configuration.Xml.AlwaysFailValidator, ValidationFramework.Tests' />
		    </property>
		  </class>
		</validationMapping>";

		private const string methodXmlString = @"<?xml version='1.0' encoding='utf-8' ?>
		<validationMapping xmlns='urn:validationFramework-validationDefinition-1.5'>
		  <class typeName='ValidationFramework.Tests.Configuration.Xml.Person, ValidationFramework.Tests'>
		 
		    <method name='SetTheAge'>
		      <overloadType typeName='System.Int32'/>
		      <parameter name='age'>
		        <rule typeName='ValidationFramework.Tests.Configuration.Xml.AlwaysFailValidator, ValidationFramework.Tests' />
		        <rule typeName='ValidationFramework.Tests.Configuration.Xml.AlwaysFailValidator, ValidationFramework.Tests' />
		        <rule typeName='ValidationFramework.Tests.Configuration.Xml.AlwaysFailValidator, ValidationFramework.Tests' />
		        <rule typeName='ValidationFramework.Tests.Configuration.Xml.AlwaysFailValidator, ValidationFramework.Tests' />
		      </parameter>
		    </method>
		    <method name='SetTheAge2'>
		      <parameter name='age'>
		        <rule typeName='ValidationFramework.Tests.Configuration.Xml.AlwaysFailValidator, ValidationFramework.Tests' />
		        <rule typeName='ValidationFramework.Tests.Configuration.Xml.AlwaysFailValidator, ValidationFramework.Tests' />
		        <rule typeName='ValidationFramework.Tests.Configuration.Xml.AlwaysFailValidator, ValidationFramework.Tests' />
		        <rule typeName='ValidationFramework.Tests.Configuration.Xml.AlwaysFailValidator, ValidationFramework.Tests'/>
		      </parameter>
		    </method>
		  </class>
		</validationMapping>";

		public class AddAssembly : ConfigurationServiceTestBase
		{
			[Test]
			public void Should_Scan_All_EmbeddedFiles_For_Those_Ending_In_validation_dot_xml()
			{
				// Arrange
				var type = typeof(Person);
				var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");

				var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
				var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");

				var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
				var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");

				// Act
				_xmlService.AddAssemblyResources(typeof(XmlConfigurationServiceTest).Assembly);

				// Assert
				Assert.AreEqual(4, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);
			}

			[Test]
			public void ByName()
			{
				// Arrange
				var type = typeof(Person);
				var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");

				var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
				var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");

				var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
				var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");

				// Act
				 _xmlService.AddAssemblyResources("ValidationFramework.Tests");

				// Assert
				Assert.AreEqual(4, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);

			}

			[Test]
			[ExpectedException(typeof(FileNotFoundException))]
			public void ExceptionInvalidName()
			{
				_xmlService.AddAssemblyResources("InvalidAssemblyName");
			}

		}

		public class AddClass : ConfigurationServiceTestBase
		{
			[Test]
			public void Simple()
			{
				// Arrange
				var type = typeof(Person);
				var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");

				var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
				var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");

				var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
				var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");

				// Act
				 _xmlService.AddResource(type);

				// Assert
				Assert.AreEqual(4, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);
			}

		}

		public class AddXmlFile : ConfigurationServiceTestBase
		{
			[Test]
			[ExpectedException(typeof(FileNotFoundException))]
			public void ExceptionNotExist()
			{
				_xmlService.AddXmlFile(@"Invalid.xml");
			}

			[Test]
			public void Simple()
			{
				// Arrange
				var type = typeof(Person);
				var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");

				var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
				var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");

				var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
				var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");

				// Act
				var path = WriteXmlConfig();
				 _xmlService.AddXmlFile(path);

				// Assert
				Assert.AreEqual(4, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);
			}


		}
		private static string WriteXmlConfig()
		{
			using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("ValidationFramework.Tests.Configuration.Xml.Person.validation.xml"))
			{
				using (var streamReader = new StreamReader(stream))
				{
					var fullPath = Path.GetFullPath("temp.xml");
					using (var streamWriter = new StreamWriter(fullPath, false))
					{
						streamWriter.Write(streamReader.ReadToEnd());
					}
					return fullPath;
				}
			}
		}

		public class AddUrl : ConfigurationServiceTestBase
		{
			[Test]
			public void AddUrlValid()
			{
				// Arrange
				var type = typeof(Person);
				var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");

				var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
				var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");

				var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
				var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");

				// Act
				var path = WriteXmlConfig();
				 _xmlService.AddXmlAtUrl(path);

				// Assert
				Assert.AreEqual(4, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);
			}


			[Test]
			public void AddUriValid()
			{
				// Arrange
				var type = typeof(Person);
				var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");

				var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
				var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");

				var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
				var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");

				// Act
				var path = WriteXmlConfig();
				 _xmlService.AddXmlAtUrl(new Uri(path));

				// Assert
				Assert.AreEqual(4, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);
			}
		}

		public class AddDirectory : ConfigurationServiceTestBase
		{
			[Test]
			public void Simple()
			{
				try
				{
					// Arrange
					var path = WriteXmlConfig();
					Directory.CreateDirectory("temp");
					File.Move(path, "temp/temp.validation.xml");

					// Act	
					 _xmlService.AddDirectory(new DirectoryInfo(@"./"));

					// Assert
					var type = typeof(Person);
					var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");
					Assert.AreEqual(4, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);

					var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
					var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");
					Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);

					var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
					var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");
					Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);
				}
				finally
				{
					if (Directory.Exists("temp"))
					{
						if (File.Exists("temp/temp.validation.xml"))
						{
							File.Delete("temp/temp.validation.xml");
							Directory.Delete("temp");
						}
					}
				}

			}
		}

		public class AddXmlString : ConfigurationServiceTestBase
		{

			[Test]
			public void Method()
			{
				// Act
				 _xmlService.AddXmlString(methodXmlString);

				// Assert
				var type = typeof(Person);
				
				var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
				var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);

				var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
				var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);
			}


			[Test]
			public void Property()
			{
				// Act
				 _xmlService.AddXmlString(propertyXmlString);

				// Assert
				var type = typeof(Person);
				var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");
				Assert.AreEqual(1, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);
			}
		}
#if (!SILVERLIGHT)
		public class AddDocument : ConfigurationServiceTestBase
		{
			[Test]
			public void Method()
			{
				// Arrange
				var xmlDocument = new XmlDocument();
				xmlDocument.LoadXml(methodXmlString);

				// Act
				 _xmlService.AddDocument(xmlDocument);

				// Assert
				var type = typeof(Person);

				var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
				var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);

				var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
				var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);
			}


			[Test]
			public void Property()
			{
				// Arrange
				var xmlDocument = new XmlDocument();
				xmlDocument.LoadXml(propertyXmlString);

				// Act
				 _xmlService.AddDocument(xmlDocument);

				// Assert
				var type = typeof(Person);
				var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");
				Assert.AreEqual(1, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);
			}

		}
#endif
		public class AddXmlReader : ConfigurationServiceTestBase
		{
			[Test]
			public void Property()
			{
				// Arramge
				var xmlTextReader = XmlReader.Create(new StringReader(propertyXmlString));

				// Act
				 _xmlService.Add(xmlTextReader);

				// Assert
				var type = typeof(Person);
				var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");
				Assert.AreEqual(1, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);
			}


			[Test]
			public void Method()
			{
				// Arrange
				var xmlTextReader = XmlReader.Create(new StringReader(methodXmlString));

				// Act
				 _xmlService.Add(xmlTextReader);

				// Assert
				var type = typeof(Person);

				var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
				var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);

				var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
				var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);
			}
		}

		public class AddInputStream : ConfigurationServiceTestBase
		{

			[Test]
			public void Simple()
			{
				using (var stream = GetType().Assembly.GetManifestResourceStream("ValidationFramework.Tests.Configuration.Xml.Person.validation.xml"))
				{
					// Act
					_xmlService.AddInputStream(stream);
				}

				// Assert
				var type = typeof(Person);
				var agePropertyKey = new PropertyKey(new TypeKey(type.TypeHandle), "Age");
				Assert.AreEqual(4, _typePolicyRepository.GetPropertyPolicy(agePropertyKey).RuleCount);

				var setTheAgeIntMethodHandle = type.GetMethod("SetTheAge", new[] { typeof(int) }).MethodHandle;
				var ageParameterKey = new ParameterKey(new MethodKey(setTheAgeIntMethodHandle), "age");
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey).RuleCount);

				var setTheAge2MethodHandle = type.GetMethod("SetTheAge2").MethodHandle;
				var ageParameterKey2 = new ParameterKey(new MethodKey(setTheAge2MethodHandle), "age");
				Assert.AreEqual(4, _methodPolicyRepository.GetParameterPolicy(ageParameterKey2).RuleCount);
			}

		}
		[TestFixture]
		public class GetRule : ConfigurationServiceTestBase
		{

#if (WindowsCE)
            [Test]
            [ExpectedException(typeof(ArgumentException), ExpectedMessage = "Could not load IRuleConfigReader type. Tried 'CompactValidationFramework.Tests.Configuration.NoConfigReaderRuleConfigReader,ValidationFramework.Tests, Version=1.1.0.0, Culture=neutral, PublicKeyToken=2f07bd240683f08a'.")]
            public void ExceptionNoConfigReader()
            {
                const string validatorXml = "<rule validationExpression='aaaaa' typeName='ValidationFramework.Tests.Configuration.NoConfigReaderRule,CompactValidationFramework.Tests'/>";
				var ruleData = (RuleData.Read(validatorXml.ToXmlReader()));
                XmlConfigurationService.GetRule(ruleData, TypePointers.StringTypeHandle);
            }

#else
			[Test]
			[ExpectedException(typeof(ArgumentException), ExpectedMessage = "Could not load type. Tried 'ValidationFramework.Tests.Configuration.NoConfigReaderRule,ValidationFramework.Tests'.")]
			public void Should_Throw_When_Not_Able_To_Resolve_Addr_Type()
			{
				const string validatorXml = "<rule typeName='ValidationFramework.Tests.Configuration.NoConfigReaderRule,ValidationFramework.Tests'/>";
				var ruleData = (RuleData.Read(validatorXml.ToXmlReader()));
				_xmlService.GetRule(ruleData, FakeRuleContext.GetContext());
			}
#endif
		}
	}
}
